// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable InconsistentNaming

/* Location.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Drawing;
using System.Runtime.Serialization;

#endregion

#nullable enable

namespace AM.Drawing.Charting;

/// <summary>
/// A class than contains information about the position
/// of an object on the graph.
/// </summary>
[Serializable]
public class Location
    : ICloneable, ISerializable
{
    #region Properties

    /// <summary>
    /// A horizontal alignment parameter for this object specified
    /// using the <see cref="AlignH"/> enum type.
    /// </summary>
    public AlignH AlignH { get; set; }

    /// <summary>
    /// A vertical alignment parameter for this object specified
    /// using the <see cref="AlignV"/> enum type.
    /// </summary>
    public AlignV AlignV { get; set; }

    /// <summary>
    /// The coordinate system to be used for defining the object position
    /// </summary>
    /// <value> The coordinate system is defined with the <see cref="CoordType"/>
    /// enum</value>
    public CoordType CoordinateFrame { get; set; }

    /// <summary>
    /// The x position of the object.
    /// </summary>
    /// <remarks>
    /// The units of this position
    /// are specified by the <see cref="CoordinateFrame"/> property.
    /// The object will be aligned to this position based on the
    /// <see cref="AlignH"/> property.
    /// </remarks>
    public double X { get; set; }

    /// <summary>
    /// The y position of the object.
    /// </summary>
    /// <remarks>
    /// The units of this position
    /// are specified by the <see cref="CoordinateFrame"/> property.
    /// The object will be aligned to this position based on the
    /// <see cref="AlignV"/> property.
    /// </remarks>
    public double Y { get; set; }

    /// <summary>
    /// The x1 position of the object (an alias for the x position).
    /// </summary>
    /// <remarks>
    /// The units of this position
    /// are specified by the <see cref="CoordinateFrame"/> property.
    /// The object will be aligned to this position based on the
    /// <see cref="AlignH"/> property.
    /// </remarks>
    public double X1
    {
        get => X;
        set => X = value;
    }

    /// <summary>
    /// The y1 position of the object (an alias for the y position).
    /// </summary>
    /// <remarks>
    /// The units of this position
    /// are specified by the <see cref="CoordinateFrame"/> property.
    /// The object will be aligned to this position based on the
    /// <see cref="AlignV"/> property.
    /// </remarks>
    public double Y1
    {
        get => Y;
        set => Y = value;
    }

    /// <summary>
    /// The width of the object.
    /// </summary>
    /// <remarks>
    /// The units of this position are specified by the
    /// <see cref="CoordinateFrame"/> property.
    /// </remarks>
    public double Width { get; set; }

    /// <summary>
    /// The height of the object.
    /// </summary>
    /// <remarks>
    /// The units of this position are specified by the
    /// <see cref="CoordinateFrame"/> property.
    /// </remarks>
    public double Height { get; set; }

    /// <summary>
    /// The x2 position of the object.
    /// </summary>
    /// <remarks>
    /// The units of this position are specified by the
    /// <see cref="CoordinateFrame"/> property.
    /// The object will be aligned to this position based on the
    /// <see cref="AlignH"/> property.  This position is only used for
    /// objects such as <see cref="ArrowObj"/>, where it makes sense
    /// to have a second coordinate.  Note that the X2 position is stored
    /// internally as a <see cref="Width"/> offset from <see cref="X"/>.
    /// </remarks>
    public double X2 => X + Width;

    /// <summary>
    /// The y2 position of the object.
    /// </summary>
    /// <remarks>
    /// The units of this position
    /// are specified by the <see cref="CoordinateFrame"/> property.
    /// The object will be aligned to this position based on the
    /// <see cref="AlignV"/> property.  This position is only used for
    /// objects such as <see cref="ArrowObj"/>, where it makes sense
    /// to have a second coordinate.  Note that the Y2 position is stored
    /// internally as a <see cref="Height"/> offset from <see cref="Y"/>.
    /// </remarks>
    public double Y2 => Y + Height;

    /// <summary>
    /// The <see cref="RectangleF"/> for this object as defined by the
    /// <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and
    /// <see cref="Height"/> properties.
    /// </summary>
    /// <remarks>
    /// Note that this method reduces the precision of the location coordinates from double
    /// precision to single precision.  In some cases, such as <see cref="AxisType.Date" />, it
    /// may affect the resolution of the point location.
    /// </remarks>
    /// <value>A <see cref="RectangleF"/> in <see cref="CoordinateFrame"/>
    /// units.</value>
    public RectangleF Rect
    {
        get => new ( (float) X, (float) Y, (float) Width, (float) Height);
        set
        {
            X = value.X;
            Y = value.Y;
            Width = value.Width;
            Height = value.Height;
        }
    }

    /// <summary>
    /// The top-left <see cref="PointF"/> for this <see cref="Location"/>.
    /// </summary>
    /// <remarks>
    /// Note that this method reduces the precision of the location coordinates from double
    /// precision to single precision.  In some cases, such as <see cref="AxisType.Date" />, it
    /// may affect the resolution of the point location.
    /// </remarks>
    /// <value>A <see cref="PointF"/> in <see cref="CoordinateFrame"/> units.</value>
    public PointF TopLeft
    {
        get => new ((float) X, (float) Y);
        set
        {
            X = value.X;
            Y = value.Y;
        }
    }

    /// <summary>
    /// The bottom-right <see cref="PointF"/> for this <see cref="Location"/>.
    /// </summary>
    /// <remarks>
    /// Note that this method reduces the precision of the location coordinates from double
    /// precision to single precision.  In some cases, such as <see cref="AxisType.Date" />, it
    /// may affect the resolution of the point location.
    /// </remarks>
    /// <value>A <see cref="PointF"/> in <see cref="CoordinateFrame"/> units.</value>
    public PointF BottomRight => new ((float) X2, (float) Y2);

    #endregion

    #region Constructors

    /// <summary>
    /// Default constructor for the <see cref="Location"/> class.
    /// </summary>
    public Location() : this (0, 0, CoordType.ChartFraction)
    {
    }

    /// <summary>
    /// Constructor for the <see cref="Location"/> class that specifies the
    /// x, y position and the <see cref="CoordType"/>.
    /// </summary>
    /// <remarks>
    /// The (x,y) position corresponds to the top-left corner;
    /// </remarks>
    /// <param name="x">The x position, specified in units of <see paramref="coordType"/>.
    /// </param>
    /// <param name="y">The y position, specified in units of <see paramref="coordType"/>.
    /// </param>
    /// <param name="coordType">The <see cref="CoordType"/> enum that specifies the
    /// units for <see paramref="x"/> and <see paramref="y"/></param>
    public Location (double x, double y, CoordType coordType) :
        this (x, y, coordType, AlignH.Left, AlignV.Top)
    {
    }

    /// <summary>
    /// Constructor for the <see cref="Location"/> class that specifies the
    /// x, y position and the <see cref="CoordType"/>.
    /// </summary>
    /// <remarks>
    /// The (x,y) position corresponds to the top-left corner;
    /// </remarks>
    /// <param name="x">The x position, specified in units of <see paramref="coordType"/>.
    /// </param>
    /// <param name="y">The y position, specified in units of <see paramref="coordType"/>.
    /// </param>
    /// <param name="coordType">The <see cref="CoordType"/> enum that specifies the
    /// units for <see paramref="x"/> and <see paramref="y"/></param>
    /// <param name="alignH">The <see cref="Charting.AlignH"/> enum that specifies
    /// the horizontal alignment of the object with respect to the (x,y) location</param>
    /// <param name="alignV">The <see cref="Charting.AlignV"/> enum that specifies
    /// the vertical alignment of the object with respect to the (x,y) location</param>
    public Location (double x, double y, CoordType coordType, AlignH alignH, AlignV alignV)
    {
        X = x;
        Y = y;
        Width = 0;
        Height = 0;
        CoordinateFrame = coordType;
        AlignH = alignH;
        AlignV = alignV;
    }

    /// <summary>
    /// Constructor for the <see cref="Location"/> class that specifies the
    /// (x, y), (width, height), and the <see cref="CoordType"/>.
    /// </summary>
    /// <remarks>
    /// The (x,y) position
    /// corresponds to the starting position, the (x2, y2) coorresponds to the ending position
    /// (typically used for <see cref="ArrowObj"/>'s).
    /// </remarks>
    /// <param name="x">The x position, specified in units of <see paramref="coordType"/>.
    /// </param>
    /// <param name="y">The y position, specified in units of <see paramref="coordType"/>.
    /// </param>
    /// <param name="width">The width, specified in units of <see paramref="coordType"/>.
    /// </param>
    /// <param name="height">The height, specified in units of <see paramref="coordType"/>.
    /// </param>
    /// <param name="coordType">The <see cref="CoordType"/> enum that specifies the
    /// units for <see paramref="x"/> and <see paramref="y"/></param>
    /// <param name="alignH">The <see cref="Charting.AlignH"/> enum that specifies
    /// the horizontal alignment of the object with respect to the (x,y) location</param>
    /// <param name="alignV">The <see cref="Charting.AlignV"/> enum that specifies
    /// the vertical alignment of the object with respect to the (x,y) location</param>
    public Location (double x, double y, double width, double height,
        CoordType coordType, AlignH alignH, AlignV alignV) :
        this (x, y, coordType, alignH, alignV)
    {
        Width = width;
        Height = height;
    }

    /// <summary>
    /// The Copy Constructor
    /// </summary>
    /// <param name="rhs">The <see cref="Location"/> object from which to copy</param>
    public Location (Location rhs)
    {
        X = rhs.X;
        Y = rhs.Y;
        Width = rhs.Width;
        Height = rhs.Height;
        CoordinateFrame = rhs.CoordinateFrame;
        AlignH = rhs.AlignH;
        AlignV = rhs.AlignV;
    }

    /// <summary>
    /// Implement the <see cref="ICloneable" /> interface in a typesafe manner by just
    /// calling the typed version of <see cref="Clone" />
    /// </summary>
    /// <returns>A deep copy of this object</returns>
    object ICloneable.Clone()
    {
        return Clone();
    }

    /// <summary>
    /// Typesafe, deep-copy clone method.
    /// </summary>
    /// <returns>A new, independent copy of this class</returns>
    public Location Clone()
    {
        return new Location (this);
    }

    #endregion

    #region Serialization

    /// <summary>
    /// Current schema value that defines the version of the serialized file
    /// </summary>
    public const int schema = 10;

    /// <summary>
    /// Constructor for deserializing objects
    /// </summary>
    /// <param name="info">A <see cref="SerializationInfo"/> instance that defines the serialized data
    /// </param>
    /// <param name="context">A <see cref="StreamingContext"/> instance that contains the serialized data
    /// </param>
    protected Location (SerializationInfo info, StreamingContext context)
    {
        // The schema value is just a file version parameter.  You can use it to make future versions
        // backwards compatible as new member variables are added to classes
        info.GetInt32 ("schema");

        AlignV = (AlignV) info.GetValue ("alignV", typeof (AlignV))!;
        AlignH = (AlignH) info.GetValue ("alignH", typeof (AlignH))!;
        X = info.GetDouble ("x");
        Y = info.GetDouble ("y");
        Width = info.GetDouble ("width");
        Height = info.GetDouble ("height");
        CoordinateFrame = (CoordType) info.GetValue ("coordinateFrame", typeof (CoordType))!;
    }

    /// <summary>
    /// Populates a <see cref="SerializationInfo"/> instance with the data needed to serialize the target object
    /// </summary>
    /// <param name="info">A <see cref="SerializationInfo"/> instance that defines the serialized data</param>
    /// <param name="context">A <see cref="StreamingContext"/> instance that contains the serialized data</param>
    public virtual void GetObjectData
        (
            SerializationInfo info,
            StreamingContext context
        )
    {
        info.AddValue ("schema", schema);
        info.AddValue ("alignV", AlignV);
        info.AddValue ("alignH", AlignH);
        info.AddValue ("x", X);
        info.AddValue ("y", Y);
        info.AddValue ("width", Width);
        info.AddValue ("height", Height);
        info.AddValue ("coordinateFrame", CoordinateFrame);
    }

    #endregion

    #region Methods

    /// <summary>
    /// Transform this <see cref="Location"/> object to display device
    /// coordinates using the properties of the specified <see cref="GraphPane"/>.
    /// </summary>
    /// <param name="pane">
    /// A reference to the <see cref="PaneBase"/> object that contains
    /// the <see cref="Axis"/> classes which will be used for the transform.
    /// </param>
    /// <returns>A point in display device coordinates that corresponds to the
    /// specified user point.</returns>
    public PointF Transform (PaneBase pane)
    {
        return Transform (pane, X, Y, CoordinateFrame);
    }

    /// <summary>
    /// Transform a data point from the specified coordinate type
    /// (<see cref="CoordType"/>) to display device coordinates (pixels).
    /// </summary>
    /// <remarks>
    /// If <see paramref="pane"/> is not of type <see cref="GraphPane"/>, then
    /// only the <see cref="CoordType.PaneFraction"/> transformation is available.
    /// </remarks>
    /// <param name="pane">
    /// A reference to the <see cref="PaneBase"/> object that contains
    /// the <see cref="Axis"/> classes which will be used for the transform.
    /// </param>
    /// <param name="x">The x coordinate that defines the point in user
    /// space.</param>
    /// <param name="y">The y coordinate that defines the point in user
    /// space.</param>
    /// <param name="coord">A <see cref="CoordType"/> type that defines the
    /// coordinate system in which the X,Y pair is defined.</param>
    /// <returns>A point in display device coordinates that corresponds to the
    /// specified user point.</returns>
    public static PointF Transform (PaneBase pane, double x, double y, CoordType coord)
    {
        return pane.TransformCoord (x, y, coord);
    }

    /// <summary>
    /// Transform this <see cref="Location"/> from the coordinate system
    /// as specified by <see cref="CoordinateFrame"/> to the device coordinates
    /// of the specified <see cref="PaneBase"/> object.
    /// </summary>
    /// <remarks>
    /// The returned
    /// <see cref="PointF"/> struct represents the top-left corner of the
    /// object that honors the <see cref="Location"/> properties.
    /// The <see cref="AlignH"/> and <see cref="AlignV"/> properties are honored in
    /// this transformation.
    /// </remarks>
    /// <param name="pane">
    /// A reference to the <see cref="PaneBase"/> object that contains
    /// the <see cref="Axis"/> classes which will be used for the transform.
    /// </param>
    /// <param name="width">The width of the object in device pixels</param>
    /// <param name="height">The height of the object in device pixels</param>
    /// <returns>The top-left corner of the object</returns>
    public PointF TransformTopLeft (PaneBase pane, float width, float height)
    {
        PointF pt = Transform (pane);

        switch (AlignH)
        {
            case AlignH.Right:
                pt.X -= width;
                break;

            case AlignH.Center:
                pt.X -= width / 2.0F;
                break;
        }

        switch (AlignV)
        {
            case AlignV.Bottom:
                pt.Y -= height;
                break;

            case AlignV.Center:
                pt.Y -= height / 2.0F;
                break;
        }

        return pt;
    }

    /// <summary>
    /// The <see cref="PointF"/> for this object as defined by the
    /// <see cref="X"/> and <see cref="Y"/>
    /// properties.
    /// </summary>
    /// <remarks>
    /// This method transforms the location to output device pixel units.
    /// The <see cref="AlignH"/> and <see cref="AlignV"/> properties are ignored for
    /// this transformation (see <see cref="TransformTopLeft(PaneBase,float,float)"/>).
    /// </remarks>
    /// <value>A <see cref="PointF"/> in pixel units.</value>
    public PointF TransformTopLeft (PaneBase pane)
    {
        return Transform (pane);
    }

    /// <summary>
    /// The <see cref="PointF"/> for this object as defined by the
    /// <see cref="X2"/> and <see cref="Y2"/> properties.
    /// </summary>
    /// <remarks>
    /// This method transforms the location to output device pixel units.
    /// The <see cref="AlignH"/> and <see cref="AlignV"/> properties are ignored for
    /// this transformation (see <see cref="TransformTopLeft(PaneBase,float,float)"/>).
    /// </remarks>
    /// <value>A <see cref="PointF"/> in pixel units.</value>
    public PointF TransformBottomRight (PaneBase pane)
    {
        return Transform (pane, X2, Y2, CoordinateFrame);
    }

    /// <summary>
    /// Transform the <see cref="RectangleF"/> for this object as defined by the
    /// <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and
    /// <see cref="Height"/> properties.
    /// </summary>
    /// <remarks>
    /// This method transforms the location to output device pixel units.
    /// The <see cref="AlignH"/> and <see cref="AlignV"/> properties are honored in
    /// this transformation.
    /// </remarks>
    /// <value>A <see cref="RectangleF"/> in pixel units.</value>
    public RectangleF TransformRect (PaneBase pane)
    {
        PointF pix1 = TransformTopLeft (pane);
        PointF pix2 = TransformBottomRight (pane);

        //PointF pix3 = TransformTopLeft( pane, pix2.X - pix1.X, pix2.Y - pix1.Y );

        return new RectangleF (pix1.X, pix1.Y, Math.Abs (pix2.X - pix1.X), Math.Abs (pix2.Y - pix1.Y));
    }

    #endregion
}
